#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_SIZE,
	A_ENC,
	A_RATE,
	A_CHAN
};

struct afile {
	off_t		header;
	size_t		count;
	uint32_t	size;
	uint32_t	encoding;
	uint32_t	rate;
	uint32_t	channels;
	int		first;
	int		fd;
};

const struct a_lookup stab[] = {
	{ "size", A_SIZE },
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

static ssize_t	writehdr(struct afile *);
static void	lput2(void *, uint16_t);
static void	lput4(void *, uint32_t);

struct afile *
af_open(const char *file)
{
	struct afile *af;
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	if ((af = af_fdopen(fd)) == NULL)
		(void)close(fd);
	return (af);
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EINVAL, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->header = -1;
	a->count = 0;
	a->size = UINT32_MAX;
	a->encoding = AU_PCM16;
	a->rate = 0;
	a->channels = 0;
	a->first = 1;
	a->fd = fd;
	return (a);
}

void
af_close(struct afile *a)
{
	uint8_t z = 0;

	if (a == NULL)
		return;
	if (a->size & 1)
		(void)nwrite(a->fd, &z, 1);
	if (a->header >= 0 &&
	    lseek(a->fd, a->header, SEEK_SET) == a->header) {
		a->size = a->count;
		(void)writehdr(a);
	}
	(void)close(a->fd);
	free(a);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			*va_arg(ap, uint32_t *) = a->size;
			break;
		case A_ENC:
			if (a->encoding == 0)
				goto err;
			*va_arg(ap, uint32_t *) = a->encoding;
			break;
		case A_RATE:
			if (a->rate == 0)
				goto err;
			*va_arg(ap, uint32_t *) = a->rate;
			break;
		case A_CHAN:
			if (a->channels == 0)
				goto err;
			*va_arg(ap, uint32_t *) = a->channels;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			a->size = va_arg(ap, uint32_t);
			break;
		case A_ENC:
			a->encoding = va_arg(ap, uint32_t);
			if (a->encoding != AU_ULAW &&
			    a->encoding != AU_PCM8 &&
			    a->encoding != AU_PCM16 &&
			    a->encoding != AU_PCM24 &&
			    a->encoding != AU_PCM32 &&
			    a->encoding != AU_FLOAT32 &&
			    a->encoding != AU_FLOAT64 &&
			    a->encoding != AU_ALAW)
				goto err;
			break;
		case A_RATE:
			a->rate = va_arg(ap, uint32_t);
			break;
		case A_CHAN:
			a->channels = va_arg(ap, uint32_t);
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

ssize_t
af_write(struct afile *a, void *b, size_t size)
{
	ssize_t r;

	if (a == NULL || b == NULL || a->fd < 0)
		ERR(EINVAL, -1);
	if (a->first) {
		if ((r = writehdr(a)) <= 0)
			return (r);
		a->first = 0;
	}
	(void)au_encbswap(a->encoding, b, size);
	if ((r = nwrite(a->fd, b, MIN(size, a->size))) > 0) {
		a->count += r;
		if (a->size != UINT32_MAX)
			a->size -= r;
	}
	return (r);
}

ssize_t
writehdr(struct afile *a)
{
	uint8_t b[58];
	size_t i = 0;

	assert(a != NULL);
	if (a->size == UINT32_MAX &&
	    (a->header = lseek(a->fd, 0, SEEK_CUR)) < 0)
		return (-1);
	(void)memcpy(b + i, "RIFF", 4);
	i += 4;
	if (a->size == UINT32_MAX)
		(void)memset(b + i, 0xFF, 4);
	else if (a->size >= UINT32_MAX - 51)
		ERR(EINVAL, -1);
	else
		lput4(b + i, (a->size + 51) & ~UINT32_C(1));
	i += 4;
	(void)memcpy(b + i, "WAVE", 4);
	i += 4;
	(void)memcpy(b + i, "fmt ", 4);
	i += 4;
	lput4(b + i, 16);
	i += 4;
	switch (a->encoding) {
	case AU_ULAW:
		lput2(b + i, 7);
		break;
	case AU_PCM8:
		lput2(b + i, 1);
		break;
	case AU_PCM16:
		lput2(b + i, 1);
		break;
	case AU_PCM24:
		lput2(b + i, 1);
		break;
	case AU_PCM32:
		lput2(b + i, 1);
		break;
	case AU_FLOAT32:
		lput2(b + i, 3);
		break;
	case AU_FLOAT64:
		lput2(b + i, 3);
		break;
	case AU_ALAW:
		lput2(b + i, 6);
		break;
	default:
		ERR(EINVAL, -1);
	}
	i += 2;
	lput2(b + i, a->channels);
	i += 2;
	lput4(b + i, a->rate);
	i += 4;
	lput4(b + i, a->rate * a->channels * au_encsize(a->encoding));
	i += 4;
	lput2(b + i, a->channels * au_encsize(a->encoding));
	i += 2;
	lput2(b + i, au_encsize(a->encoding) * 8);
	i += 2;
	(void)memcpy(b + i, "fact", 4);
	i += 4;
	lput4(b + i, 4);
	i += 4;
	if (a->size == UINT32_MAX)
		(void)memset(b + i, 0xFF, 4);
	else
		lput4(b + i, a->size / au_encsize(a->encoding) /
		    a->channels);
	i += 4;
	(void)memcpy(b + i, "data", 4);
	i += 4;
	if (a->size == UINT32_MAX)
		(void)memset(b + i, 0xFF, 4);
	else
		lput4(b + i, a->size);
	i += 4;
	return (nwrite(a->fd, b, i));
}

void
lput2(void *p, uint16_t v)
{

	assert(p != NULL);
	((uint8_t *)p)[0] = v >> 0;
	((uint8_t *)p)[1] = v >> 8;
}

void
lput4(void *p, uint32_t v)
{
	uint8_t *u = p;

	assert(p != NULL);
	u[0] = v >> 0;
	u[1] = v >> 8;
	u[2] = v >> 16;
	u[3] = v >> 24;
}
